﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Input;
using StyleMVVM.View;
#if NETFX_CORE
using Windows.System;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Documents;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;

#else
using System.Windows;
using System.Windows.Controls;

#endif
// The Templated Control item template is documented at http://go.microsoft.com/fwlink/?LinkId=234235

namespace StyleMVVM.Controls.Input
{
	public enum IntegerFormat
	{
		None,
		Integer,
		Nullable
	}

	public sealed class IntegerTextBox
	{
		public static bool GetShowRangeWarnings(DependencyObject obj)
		{
			return (bool)obj.GetValue(InternalShowRangeWarningsProperty);
		}

		public static void SetShowRangeWarnings(DependencyObject obj, bool value)
		{
			obj.SetValue(InternalShowRangeWarningsProperty, value);
		}

		public static DependencyProperty ShowRangeWarningsProperty
		{
			get { return InternalShowRangeWarningsProperty; }
		}

		// Using a DependencyProperty as the backing store for ShowRangeWarnings.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalShowRangeWarningsProperty =
			DependencyProperty.RegisterAttached("ShowRangeWarnings",
			                                    typeof(bool),
			                                    typeof(IntegerTextBox),
			                                    new PropertyMetadata(true));

		public static IntegerFormat GetFormat(DependencyObject obj)
		{
			return (IntegerFormat)obj.GetValue(InternalFormatProperty);
		}

		public static void SetFormat(DependencyObject obj, IntegerFormat value)
		{
			obj.SetValue(InternalFormatProperty, value);
		}

		public static DependencyProperty FormatProperty
		{
			get { return InternalFormatProperty; }
		}

		// Using a DependencyProperty as the backing store for Format.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalFormatProperty =
			DependencyProperty.RegisterAttached("Format",
			                                    typeof(IntegerFormat),
			                                    typeof(IntegerTextBox),
			                                    new PropertyMetadata(IntegerFormat.None, FormatChanged));

		private static void FormatChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			TextBox textBox = d as TextBox;

			if (textBox != null)
			{
				textBox.TextChanged -= TextBoxOnTextChanged;
				textBox.KeyDown -= TextBoxOnKeyDown;

				if ((IntegerFormat)e.NewValue != IntegerFormat.None)
				{
					textBox.TextChanged += TextBoxOnTextChanged;
					textBox.KeyDown += TextBoxOnKeyDown;
				}
			}
		}

		private static void TextBoxOnTextChanged(object sender, TextChangedEventArgs textChangedEventArgs)
		{
			TextBox textBox = sender as TextBox;

			if (textBox != null)
			{
				if (!string.IsNullOrEmpty(textBox.Text))
				{
					int newValue;

					if (int.TryParse(textBox.Text, out newValue))
					{
						SetValue(textBox, newValue);
					}
				}
			}
		}

		public static int? GetValue(DependencyObject obj)
		{
			return (int?)obj.GetValue(InternalValueProperty);
		}

		public static void SetValue(DependencyObject obj, int? value)
		{
			obj.SetValue(InternalValueProperty, value);
		}

		public static DependencyProperty ValueProperty
		{
			get { return InternalValueProperty; }
		}

		// Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalValueProperty =
			DependencyProperty.RegisterAttached("Value",
			                                    typeof(int?),
			                                    typeof(IntegerTextBox),
			                                    new PropertyMetadata(null, ValueChanged));

		private static void ValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			TextBox textBox = d as TextBox;

			if (textBox != null)
			{
				int? newValue = (int?)e.NewValue;

				if (newValue.HasValue)
				{
					textBox.Text = newValue.Value.ToString();
				}
				else
				{
					textBox.Text = string.Empty;
				}
			}
		}

#if NETFX_CORE
		private static void TextBoxOnKeyDown(object sender, KeyRoutedEventArgs keyRoutedEventArgs)
		{
			TextBox tb = sender as TextBox;

			if (tb != null)
			{
				if (keyRoutedEventArgs.Key >= VirtualKey.Number0 && keyRoutedEventArgs.Key <= VirtualKey.Number9)
				{
					int keyPressed = keyRoutedEventArgs.Key - VirtualKey.Number0;

					ProcessNumberPressed(tb, keyRoutedEventArgs, keyPressed);
				}
				else if (keyRoutedEventArgs.Key >= VirtualKey.NumberPad0 && keyRoutedEventArgs.Key <= VirtualKey.NumberPad9)
				{
					int keyPressed = keyRoutedEventArgs.Key - VirtualKey.NumberPad0;

					ProcessNumberPressed(tb, keyRoutedEventArgs, keyPressed);
				}
				else if (keyRoutedEventArgs.Key == VirtualKey.Subtract || (int)keyRoutedEventArgs.Key == 189)
				{
					ProcessSubtractPressed(tb, keyRoutedEventArgs);
				}
				else if (keyRoutedEventArgs.Key == VirtualKey.Tab)
				{
					// do nothing and let it tab out
				}
				else if (keyRoutedEventArgs.Key == VirtualKey.Back)
				{
					ProcessBackPressed(tb, keyRoutedEventArgs);
				}
				else if (keyRoutedEventArgs.Key == VirtualKey.Delete)
				{
					ProcessDeletePressed(tb, keyRoutedEventArgs);
				}
				else
				{
					keyRoutedEventArgs.Handled = true;
				}
			}
		}
#else
		private static void TextBoxOnKeyDown(object sender, KeyEventArgs keyRoutedEventArgs)
		{
			TextBox tb = sender as TextBox;

			if (tb != null)
			{
				if (keyRoutedEventArgs.Key >= Key.D0 && keyRoutedEventArgs.Key <= Key.D9)
				{
					int keyPressed = keyRoutedEventArgs.Key - Key.D0;

					ProcessNumberPressed(tb, keyRoutedEventArgs, keyPressed);
				}
				else if (keyRoutedEventArgs.Key >= Key.NumPad0 && keyRoutedEventArgs.Key <= Key.NumPad9)
				{
					int keyPressed = keyRoutedEventArgs.Key - Key.NumPad0;

					ProcessNumberPressed(tb, keyRoutedEventArgs, keyPressed);
				}
				else if (keyRoutedEventArgs.Key == Key.Subtract || (int)keyRoutedEventArgs.Key == 189)
				{
					ProcessSubtractPressed(tb, keyRoutedEventArgs);
				}
				else if (keyRoutedEventArgs.Key == Key.Tab)
				{
					// do nothing and let it tab out
				}
				else if (keyRoutedEventArgs.Key == Key.Back)
				{
					ProcessBackPressed(tb, keyRoutedEventArgs);
				}
				else if (keyRoutedEventArgs.Key == Key.Delete)
				{
					ProcessDeletePressed(tb, keyRoutedEventArgs);
				}
				else
				{
					keyRoutedEventArgs.Handled = true;
				}
			}
		}
#endif

#if NETFX_CORE
		private static void ProcessNumberPressed(TextBox tb, KeyRoutedEventArgs keyRoutedEventArgs, int numberPressed)
#else
		private static void ProcessNumberPressed(TextBox tb, KeyEventArgs keyRoutedEventArgs, int numberPressed)
#endif
		{
			string newText = null;

			if (tb.SelectionLength > 0)
			{
				newText = tb.Text.Substring(0, tb.SelectionStart);

				newText += numberPressed.ToString();

				if (tb.Text.Length > tb.SelectionStart + tb.SelectionLength)
				{
					newText += tb.Text.Substring(tb.SelectionStart + tb.SelectionLength);
				}
			}
			else
			{
				newText = tb.Text.Insert(tb.SelectionStart, numberPressed.ToString());
			}

			int newValue;

			if (int.TryParse(newText, out newValue))
			{
				if (!ValueIsWithinLimits(tb, newValue))
				{
					keyRoutedEventArgs.Handled = true;
				}
			}
			else if (!string.IsNullOrEmpty(newText))
			{
				IMessageBoxService messageBoxService =
					Bootstrapper.Instance.Container.LocateByType(typeof(IMessageBoxService)) as IMessageBoxService;

				messageBoxService.Notify(string.Format("You must provide a value between {0} and {1}", int.MinValue, int.MaxValue));

				keyRoutedEventArgs.Handled = true;
			}
		}

#if NETFX_CORE
		private static void ProcessSubtractPressed(TextBox tb, KeyRoutedEventArgs keyRoutedEventArgs)
#else
		private static void ProcessSubtractPressed(TextBox tb, KeyEventArgs keyRoutedEventArgs)
#endif
		{
			int? value = GetValue(tb);

			if (value.HasValue)
			{
				int textValue = -(value.Value);
				int selectIndex = tb.SelectionStart;
				bool isNegative = value.Value < 0;

				if (ValueIsWithinLimits(tb, textValue))
				{
					tb.Text = textValue.ToString();
				}

				if (isNegative)
				{
					tb.SelectionStart = selectIndex - 1;
				}
				else
				{
					tb.SelectionStart = selectIndex + 1;
				}

				keyRoutedEventArgs.Handled = true;
			}
		}

#if NETFX_CORE
		private static void ProcessBackPressed(TextBox tb, KeyRoutedEventArgs keyRoutedEventArgs)
#else
		private static void ProcessBackPressed(TextBox tb, KeyEventArgs keyRoutedEventArgs)
#endif
		{
			string newText = null;

			if (tb.SelectionLength > 0)
			{
				newText = tb.Text.Substring(0, tb.SelectionStart);

				if (tb.Text.Length > tb.SelectionStart + tb.SelectionLength)
				{
					newText += tb.Text.Substring(tb.SelectionStart + tb.SelectionLength);
				}
			}
			else
			{
				if (tb.SelectionStart > 0)
				{
					newText = tb.Text.Substring(0, tb.SelectionStart - 1);
				}

				newText += tb.Text.Substring(tb.SelectionStart);
			}

			int newValue;

			if (int.TryParse(newText, out newValue))
			{
				if (!ValueIsWithinLimits(tb, newValue))
				{
					keyRoutedEventArgs.Handled = true;
				}
			}
			else if (!string.IsNullOrEmpty(newText))
			{
				IMessageBoxService messageBoxService =
					Bootstrapper.Instance.Container.LocateByType(typeof(IMessageBoxService)) as IMessageBoxService;

				messageBoxService.Notify(string.Format("You must provide a value between {0} and {1}", int.MinValue, int.MaxValue));

				keyRoutedEventArgs.Handled = true;
			}
		}

#if NETFX_CORE
		private static void ProcessDeletePressed(TextBox tb, KeyRoutedEventArgs keyRoutedEventArgs)
#else
		private static void ProcessDeletePressed(TextBox tb, KeyEventArgs keyRoutedEventArgs)
#endif
		{
			string newText = null;

			if (tb.SelectionLength > 0)
			{
				newText = tb.Text.Substring(0, tb.SelectionStart);

				if (tb.Text.Length > tb.SelectionStart + tb.SelectionLength)
				{
					newText += tb.Text.Substring(tb.SelectionStart + tb.SelectionLength);
				}
			}
			else
			{
				if (tb.SelectionStart > 0)
				{
					newText = tb.Text.Substring(0, tb.SelectionStart);
				}

				if (tb.Text.Length > (tb.SelectionStart + 1))
				{
					newText += tb.Text.Substring(tb.SelectionStart);
				}
			}

			int newValue;

			if (int.TryParse(newText, out newValue))
			{
				if (!ValueIsWithinLimits(tb, newValue))
				{
					keyRoutedEventArgs.Handled = true;
				}
			}
			else if (!string.IsNullOrEmpty(newText))
			{
				IMessageBoxService messageBoxService =
					Bootstrapper.Instance.Container.LocateByType(typeof(IMessageBoxService)) as IMessageBoxService;

				messageBoxService.Notify(string.Format("You must provide a value between {0} and {1}", int.MinValue, int.MaxValue));

				keyRoutedEventArgs.Handled = true;
			}
		}

		private static bool ValueIsWithinLimits(TextBox textBox, int newValue)
		{
			int? minValue = GetMinValue(textBox);

			if (minValue.HasValue && newValue < minValue)
			{
				if (GetShowRangeWarnings(textBox))
				{
					IMessageBoxService messageBoxService =
						Bootstrapper.Instance.Container.LocateByType(typeof(IMessageBoxService)) as IMessageBoxService;

					messageBoxService.Notify("You must provide a value greater than: " + minValue.Value);
				}

				return false;
			}

			int? maxValue = GetMaxValue(textBox);

			if (maxValue.HasValue && newValue > maxValue)
			{
				if (GetShowRangeWarnings(textBox))
				{
					IMessageBoxService messageBoxService =
						Bootstrapper.Instance.Container.LocateByType(typeof(IMessageBoxService)) as IMessageBoxService;

					messageBoxService.Notify("You must provide a value less than: " + maxValue.Value);
				}

				return false;
			}

			return true;
		}

		public static int? GetMinValue(DependencyObject obj)
		{
			return (int?)obj.GetValue(InternalMinValueProperty);
		}

		public static void SetMinValue(DependencyObject obj, int? value)
		{
			obj.SetValue(InternalMinValueProperty, value);
		}

		public static DependencyProperty MinValueProperty
		{
			get { return InternalMinValueProperty; }
		}

		// Using a DependencyProperty as the backing store for MinValue.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalMinValueProperty =
			DependencyProperty.RegisterAttached("MinValue",
			                                    typeof(int?),
			                                    typeof(IntegerTextBox),
			                                    new PropertyMetadata(null, MinValueChanged));

		private static void MinValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (e.NewValue != null)
			{
				int? newValue = (int?)e.NewValue;
				int? dValue = GetValue(d);

				if (dValue.HasValue && dValue.Value < newValue.Value)
				{
					SetValue(d, newValue);
				}
			}
		}

		public static int? GetMaxValue(DependencyObject obj)
		{
			return (int?)obj.GetValue(InternalMaxValueProperty);
		}

		public static void SetMaxValue(DependencyObject obj, int? value)
		{
			obj.SetValue(InternalMaxValueProperty, value);
		}

		public static DependencyProperty MaxValueProperty
		{
			get { return InternalMaxValueProperty; }
		}

		// Using a DependencyProperty as the backing store for MaxValue.  This enables animation, styling, binding, etc...
		internal static readonly DependencyProperty InternalMaxValueProperty =
			DependencyProperty.RegisterAttached("MaxValue",
			                                    typeof(int?),
			                                    typeof(IntegerTextBox),
			                                    new PropertyMetadata(null, MaxValueChanged));

		private static void MaxValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
		{
			if (e.NewValue != null)
			{
				int? newValue = (int?)e.NewValue;
				int? dValue = GetValue(d);

				if (dValue.HasValue && dValue.Value > newValue.Value)
				{
					SetValue(d, newValue);
				}
			}
		}
	}
}